/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: XObject.java,v 1.2.4.1 2005/09/14 20:34:45 jeffsuttor Exp $
 */
package com.sun.org.apache.xpath.internal.objects;

import java.io.Serializable;

import com.sun.org.apache.xalan.internal.res.XSLMessages;
import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMIterator;
import com.sun.org.apache.xml.internal.utils.XMLString;
import com.sun.org.apache.xpath.internal.Expression;
import com.sun.org.apache.xpath.internal.ExpressionOwner;
import com.sun.org.apache.xpath.internal.NodeSetDTM;
import com.sun.org.apache.xpath.internal.XPathContext;
import com.sun.org.apache.xpath.internal.XPathException;
import com.sun.org.apache.xpath.internal.XPathVisitor;
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;

import org.w3c.dom.DocumentFragment;
import org.w3c.dom.NodeList;
import org.w3c.dom.traversal.NodeIterator;

/**
 * This class represents an XPath object, and is capable of
 * converting the object to various types, such as a string.
 * This class acts as the base class to other XPath type objects,
 * such as XString, and provides polymorphic casting capabilities.
 * @xsl.usage general
 */
public class XObject extends Expression implements Serializable, Cloneable {
    static final long serialVersionUID = -821887098985662951L;

    /**
     * The java object which this object wraps.
     *  @serial
     */
    protected Object m_obj; // This may be NULL!!!

    /**
     * Create an XObject.
     */
    public XObject() {
    }

    /**
     * Create an XObject.
     *
     * @param obj Can be any object, should be a specific type
     * for derived classes, or null.
     */
    public XObject(Object obj) {
        setObject(obj);
    }

    protected void setObject(Object obj) {
        m_obj = obj;
    }

    /**
     * For support of literal objects in xpaths.
     *
     * @param xctxt The XPath execution context.
     *
     * @return This object.
     *
     * @throws javax.xml.transform.TransformerException
     */
    public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException {
        return this;
    }

    /**
     * Specify if it's OK for detach to release the iterator for reuse.
     * This function should be called with a value of false for objects that are
     * stored in variables.
     * Calling this with a value of false on a XNodeSet will cause the nodeset
     * to be cached.
     *
     * @param allowRelease true if it is OK for detach to release this iterator
     * for pooling.
     */
    public void allowDetachToRelease(boolean allowRelease) {
    }

    /**
     * Detaches the <code>DTMIterator</code> from the set which it iterated
     * over, releasing any computational resources and placing the iterator
     * in the INVALID state. After <code>detach</code> has been invoked,
     * calls to <code>nextNode</code> or <code>previousNode</code> will
     * raise a runtime exception.
     */
    public void detach() {
    }

    /**
     * Forces the object to release it's resources.  This is more harsh than
     * detach().
     */
    public void destruct() {

        if (null != m_obj) {
            allowDetachToRelease(true);
            detach();

            setObject(null);
        }
    }

    /**
     * Reset for fresh reuse.
     */
    public void reset() {
    }

    /**
     * Directly call the
     * characters method on the passed ContentHandler for the
     * string-value. Multiple calls to the
     * ContentHandler's characters methods may well occur for a single call to
     * this method.
     *
     * @param ch A non-null reference to a ContentHandler.
     *
     * @throws org.xml.sax.SAXException
     */
    public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch) throws org.xml.sax.SAXException {
        xstr().dispatchCharactersEvents(ch);
    }

    /**
     * Create the right XObject based on the type of the object passed.  This
     * function can not make an XObject that exposes DOM Nodes, NodeLists, and
     * NodeIterators to the XSLT stylesheet as node-sets.
     *
     * @param val The java object which this object will wrap.
     *
     * @return the right XObject based on the type of the object passed.
     */
    static public XObject create(Object val) {
        return XObjectFactory.create(val);
    }

    /**
     * Create the right XObject based on the type of the object passed.
     * This function <emph>can</emph> make an XObject that exposes DOM Nodes, NodeLists, and
     * NodeIterators to the XSLT stylesheet as node-sets.
     *
     * @param val The java object which this object will wrap.
     * @param xctxt The XPath context.
     *
     * @return the right XObject based on the type of the object passed.
     */
    static public XObject create(Object val, XPathContext xctxt) {
        return XObjectFactory.create(val, xctxt);
    }

    /** Constant for NULL object type */
    public static final int CLASS_NULL = -1;

    /** Constant for UNKNOWN object type */
    public static final int CLASS_UNKNOWN = 0;

    /** Constant for BOOLEAN  object type */
    public static final int CLASS_BOOLEAN = 1;

    /** Constant for NUMBER object type */
    public static final int CLASS_NUMBER = 2;

    /** Constant for STRING object type */
    public static final int CLASS_STRING = 3;

    /** Constant for NODESET object type */
    public static final int CLASS_NODESET = 4;

    /** Constant for RESULT TREE FRAGMENT object type */
    public static final int CLASS_RTREEFRAG = 5;

    /** Represents an unresolved variable type as an integer. */
    public static final int CLASS_UNRESOLVEDVARIABLE = 600;

    /**
     * Tell what kind of class this is.
     *
     * @return CLASS_UNKNOWN
     */
    public int getType() {
        return CLASS_UNKNOWN;
    }

    /**
     * Given a request type, return the equivalent string.
     * For diagnostic purposes.
     *
     * @return type string "#UNKNOWN" + object class name
     */
    public String getTypeString() {
        return "#UNKNOWN (" + object().getClass().getName() + ")";
    }

    /**
     * Cast result object to a number. Always issues an error.
     *
     * @return 0.0
     *
     * @throws javax.xml.transform.TransformerException
     */
    public double num() throws javax.xml.transform.TransformerException {

        error(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER, new Object[] { getTypeString() }); //"Can not convert "+getTypeString()+" to a number");

        return 0.0;
    }

    /**
     * Cast result object to a number, but allow side effects, such as the
     * incrementing of an iterator.
     *
     * @return numeric value of the string conversion from the
     * next node in the NodeSetDTM, or NAN if no node was found
     */
    public double numWithSideEffects() throws javax.xml.transform.TransformerException {
        return num();
    }

    /**
     * Cast result object to a boolean. Always issues an error.
     *
     * @return false
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean bool() throws javax.xml.transform.TransformerException {

        error(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER, new Object[] { getTypeString() }); //"Can not convert "+getTypeString()+" to a number");

        return false;
    }

    /**
     * Cast result object to a boolean, but allow side effects, such as the
     * incrementing of an iterator.
     *
     * @return True if there is a next node in the nodeset
     */
    public boolean boolWithSideEffects() throws javax.xml.transform.TransformerException {
        return bool();
    }

    /**
     * Cast result object to a string.
     *
     * @return The string this wraps or the empty string if null
     */
    public XMLString xstr() {
        return XMLStringFactoryImpl.getFactory().newstr(str());
    }

    /**
     * Cast result object to a string.
     *
     * @return The object as a string
     */
    public String str() {
        return (m_obj != null) ? m_obj.toString() : "";
    }

    /**
     * Return the string representation of the object
     *
     *
     * @return the string representation of the object
     */
    public String toString() {
        return str();
    }

    /**
     * Cast result object to a result tree fragment.
     *
     * @param support XPath context to use for the conversion
     *
     * @return the objec as a result tree fragment.
     */
    public int rtf(XPathContext support) {

        int result = rtf();

        if (DTM.NULL == result) {
            DTM frag = support.createDocumentFragment();

            // %OPT%
            frag.appendTextChild(str());

            result = frag.getDocument();
        }

        return result;
    }

    /**
     * Cast result object to a result tree fragment.
     *
     * @param support XPath context to use for the conversion
     *
     * @return the objec as a result tree fragment.
     */
    public DocumentFragment rtree(XPathContext support) {
        DocumentFragment docFrag = null;
        int result = rtf();

        if (DTM.NULL == result) {
            DTM frag = support.createDocumentFragment();

            // %OPT%
            frag.appendTextChild(str());

            docFrag = (DocumentFragment) frag.getNode(frag.getDocument());
        } else {
            DTM frag = support.getDTM(result);
            docFrag = (DocumentFragment) frag.getNode(frag.getDocument());
        }

        return docFrag;
    }

    /**
     * For functions to override.
     *
     * @return null
     */
    public DocumentFragment rtree() {
        return null;
    }

    /**
     * For functions to override.
     *
     * @return null
     */
    public int rtf() {
        return DTM.NULL;
    }

    /**
     * Return a java object that's closest to the representation
     * that should be handed to an extension.
     *
     * @return The object that this class wraps
     */
    public Object object() {
        return m_obj;
    }

    /**
     * Cast result object to a nodelist. Always issues an error.
     *
     * @return null
     *
     * @throws javax.xml.transform.TransformerException
     */
    public DTMIterator iter() throws javax.xml.transform.TransformerException {

        error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST, new Object[] { getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeList!");

        return null;
    }

    /**
     * Get a fresh copy of the object.  For use with variables.
     *
     * @return This object, unless overridden by subclass.
     */
    public XObject getFresh() {
        return this;
    }

    /**
     * Cast result object to a nodelist. Always issues an error.
     *
     * @return null
     *
     * @throws javax.xml.transform.TransformerException
     */
    public NodeIterator nodeset() throws javax.xml.transform.TransformerException {

        error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST, new Object[] { getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeList!");

        return null;
    }

    /**
     * Cast result object to a nodelist. Always issues an error.
     *
     * @return null
     *
     * @throws javax.xml.transform.TransformerException
     */
    public NodeList nodelist() throws javax.xml.transform.TransformerException {

        error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST, new Object[] { getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeList!");

        return null;
    }

    /**
     * Cast result object to a nodelist. Always issues an error.
     *
     * @return The object as a NodeSetDTM.
     *
     * @throws javax.xml.transform.TransformerException
     */
    public NodeSetDTM mutableNodeset() throws javax.xml.transform.TransformerException {

        error(XPATHErrorResources.ER_CANT_CONVERT_TO_MUTABLENODELIST, new Object[] { getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeSetDTM!");

        return (NodeSetDTM) m_obj;
    }

    /**
     * Cast object to type t.
     *
     * @param t Type of object to cast this to
     * @param support XPath context to use for the conversion
     *
     * @return This object as the given type t
     *
     * @throws javax.xml.transform.TransformerException
     */
    public Object castToType(int t, XPathContext support) throws javax.xml.transform.TransformerException {

        Object result;

        switch (t) {
            case CLASS_STRING:
                result = str();
                break;
            case CLASS_NUMBER:
                result = new Double(num());
                break;
            case CLASS_NODESET:
                result = iter();
                break;
            case CLASS_BOOLEAN:
                result = new Boolean(bool());
                break;
            case CLASS_UNKNOWN:
                result = m_obj;
                break;

            // %TBD%  What to do here?
            //    case CLASS_RTREEFRAG :
            //      result = rtree(support);
            //      break;
            default:
                error(XPATHErrorResources.ER_CANT_CONVERT_TO_TYPE, new Object[] { getTypeString(), Integer.toString(t) }); //"Can not convert "+getTypeString()+" to a type#"+t);

                result = null;
        }

        return result;
    }

    /**
     * Tell if one object is less than the other.
     *
     * @param obj2 Object to compare this to
     *
     * @return True if this object is less than the given object
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean lessThan(XObject obj2) throws javax.xml.transform.TransformerException {

        // In order to handle the 'all' semantics of
        // nodeset comparisons, we always call the
        // nodeset function.  Because the arguments
        // are backwards, we call the opposite comparison
        // function.
        if (obj2.getType() == XObject.CLASS_NODESET)
            return obj2.greaterThan(this);

        return this.num() < obj2.num();
    }

    /**
     * Tell if one object is less than or equal to the other.
     *
     * @param obj2 Object to compare this to
     *
     * @return True if this object is less than or equal to the given object
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean lessThanOrEqual(XObject obj2) throws javax.xml.transform.TransformerException {

        // In order to handle the 'all' semantics of
        // nodeset comparisons, we always call the
        // nodeset function.  Because the arguments
        // are backwards, we call the opposite comparison
        // function.
        if (obj2.getType() == XObject.CLASS_NODESET)
            return obj2.greaterThanOrEqual(this);

        return this.num() <= obj2.num();
    }

    /**
     * Tell if one object is greater than the other.
     *
     * @param obj2 Object to compare this to
     *
     * @return True if this object is greater than the given object
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean greaterThan(XObject obj2) throws javax.xml.transform.TransformerException {

        // In order to handle the 'all' semantics of
        // nodeset comparisons, we always call the
        // nodeset function.  Because the arguments
        // are backwards, we call the opposite comparison
        // function.
        if (obj2.getType() == XObject.CLASS_NODESET)
            return obj2.lessThan(this);

        return this.num() > obj2.num();
    }

    /**
     * Tell if one object is greater than or equal to the other.
     *
     * @param obj2 Object to compare this to
     *
     * @return True if this object is greater than or equal to the given object
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean greaterThanOrEqual(XObject obj2) throws javax.xml.transform.TransformerException {

        // In order to handle the 'all' semantics of
        // nodeset comparisons, we always call the
        // nodeset function.  Because the arguments
        // are backwards, we call the opposite comparison
        // function.
        if (obj2.getType() == XObject.CLASS_NODESET)
            return obj2.lessThanOrEqual(this);

        return this.num() >= obj2.num();
    }

    /**
     * Tell if two objects are functionally equal.
     *
     * @param obj2 Object to compare this to
     *
     * @return True if this object is equal to the given object
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean equals(XObject obj2) {

        // In order to handle the 'all' semantics of
        // nodeset comparisons, we always call the
        // nodeset function.
        if (obj2.getType() == XObject.CLASS_NODESET)
            return obj2.equals(this);

        if (null != m_obj) {
            return m_obj.equals(obj2.m_obj);
        } else {
            return obj2.m_obj == null;
        }
    }

    /**
     * Tell if two objects are functionally not equal.
     *
     * @param obj2 Object to compare this to
     *
     * @return True if this object is not equal to the given object
     *
     * @throws javax.xml.transform.TransformerException
     */
    public boolean notEquals(XObject obj2) throws javax.xml.transform.TransformerException {

        // In order to handle the 'all' semantics of
        // nodeset comparisons, we always call the
        // nodeset function.
        if (obj2.getType() == XObject.CLASS_NODESET)
            return obj2.notEquals(this);

        return !equals(obj2);
    }

    /**
     * Tell the user of an error, and probably throw an
     * exception.
     *
     * @param msg Error message to issue
     *
     * @throws javax.xml.transform.TransformerException
     */
    protected void error(String msg) throws javax.xml.transform.TransformerException {
        error(msg, null);
    }

    /**
     * Tell the user of an error, and probably throw an
     * exception.
     *
     * @param msg Error message to issue
     * @param args Arguments to use in the message
     *
     * @throws javax.xml.transform.TransformerException
     */
    protected void error(String msg, Object[] args) throws javax.xml.transform.TransformerException {

        String fmsg = XSLMessages.createXPATHMessage(msg, args);

        // boolean shouldThrow = support.problem(m_support.XPATHPROCESSOR,
        //                                      m_support.ERROR,
        //                                      null,
        //                                      null, fmsg, 0, 0);
        // if(shouldThrow)
        {
            throw new XPathException(fmsg, this);
        }
    }

    /**
     * XObjects should not normally need to fix up variables.
     */
    public void fixupVariables(java.util.Vector vars, int globalsSize) {
        // no-op
    }

    /**
     * Cast result object to a string.
     *
     *
     * NEEDSDOC @param fsb
     * @return The string this wraps or the empty string if null
     */
    public void appendToFsb(com.sun.org.apache.xml.internal.utils.FastStringBuffer fsb) {
        fsb.append(str());
    }

    /**
     * @see com.sun.org.apache.xpath.internal.XPathVisitable#callVisitors(ExpressionOwner, XPathVisitor)
     */
    public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) {
        assertion(false, "callVisitors should not be called for this object!!!");
    }

    /**
     * @see Expression#deepEquals(Expression)
     */
    public boolean deepEquals(Expression expr) {
        if (!isSameClass(expr))
            return false;

        // If equals at the expression level calls deepEquals, I think we're
        // still safe from infinite recursion since this object overrides
        // equals.  I hope.
        if (!this.equals((XObject) expr))
            return false;

        return true;
    }

}
